#include "mpi.h"
#include "utils.h"
#include "My_Math.h"

#include <fstream.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>



#define COPYING_TAG 33
#define Register_Application_TAG 44
#define Start_Code_Coping_TAG 66
#define End_Code_Coping_TAG 77
#define RESULT_TAG 55
#define GENERAL_TAG 100
#define END_OF_APP_TAG 111
#define MATRIX_DATA_TAG 99
#define MASTER 0
#define SLAVE1 1



typedef long  (*getFuncComputer_dgtsv)(long N, long NRHS, double *DL, double *D, double *DU, double *B,long LDB);



int commSize;
int commRank;
char processorName[64];


void CMPI_Initialise(int argc, char * argv[])
{	
	int  namelen; // temporary variable
	MPI_Init(&argc,&argv);
    MPI_Comm_size(MPI_COMM_WORLD,&commSize);
    MPI_Comm_rank(MPI_COMM_WORLD,&commRank);
    MPI_Get_processor_name(processorName,&namelen);		
	cout << "\n MPI Initialised On:" << processorName
		 << "\n Machine Rank: " << commRank
		 << "\n Comm Size   : " << commSize 
		 << "\n ================================" << flush;
}
/******************************************************************************/
								//Destructor
void CMPI_Finalise()
{
	cout << "\n " << processorName << ": MPI Finalised:"<<flush;
	MPI_Finalize();
}
/******************************************************************************/
// 0 --> calculation failed
// 1 --> calculation succeeded
int MasterSendCode()
{
						// This is the master thread which coordinate tasks on the server //

	MPI_Status status;
	
	// the source dll file to send to slaves
	char src[] = "C:\\Work\\Developping\\Tests\\DLLTest\\Release\\DLLTest.dll";
	int tmp = strlen(src);

	// Signal slave 1 about a new code copying
	tmp = BUFSZ;
	MPI_Send(src, tmp, MPI_CHAR, SLAVE1, Start_Code_Coping_TAG, MPI_COMM_WORLD);
	

	// Open the DLL file and start sending it
	char            *buf;
	FILE            *fi;	
	unsigned        amount;	
	buf = new char[BUFSZ];	
	fi = fopen( src, "rb");
	if  (fi == NULL)
	{		
		if (fi != NULL) fclose(fi);	
		cout << "\n Error Opening the file " << flush;
		
		// End DLL Copying
		tmp = -1;
		MPI_Send(&tmp, 1, MPI_INT, SLAVE1, End_Code_Coping_TAG, MPI_COMM_WORLD);

		return 0;
	}

	
	tmp = 1; // means the file still has some bytes to be sent
	do
	{
		amount = fread( buf, sizeof(char), BUFSZ, fi );
		if (amount)
		{			
			// Send the buffer Size
			MPI_Send(&amount, 1, MPI_INT, SLAVE1, End_Code_Coping_TAG, MPI_COMM_WORLD);			
			// Send the Buffer data
			MPI_Send(buf, amount, MPI_CHAR, SLAVE1, COPYING_TAG, MPI_COMM_WORLD);			
		}
	}while (amount == BUFSZ); // when amount read is < BUFSZ, copy is done	
	fclose(fi);



	// Signal End of DLL Copy	
	tmp = -1;
	MPI_Send(&tmp, 1, MPI_INT, SLAVE1, End_Code_Coping_TAG, MPI_COMM_WORLD);	


	delete[] buf;

	// wait until slave signals "code copied successfully"
	MPI_Recv(&tmp, 1, MPI_INT, SLAVE1, End_Code_Coping_TAG, MPI_COMM_WORLD, &status);	

	// add file to the firewall safe list
	//AddApplicationToFireWall(src);


	if(tmp == 1) // slave 1 received the dll file successfully
	{
		cout << "\n Slave 1 Received the DLL Successfully" << flush;
	}
	else  // something wrong happen at slave 1
	{
		cout << "\n Slave 1 failed to Received the DLL " << flush;
	}	
	return 1;
}
/******************************************************************************/
void MasterComputeDGTSV()
{
	
	//cout << "\n In MasterComputeDGTSV()" << flush;
	
	// Tridiagonal matrix and the B vector
	CMy_Matrix *triD, *b;
	triD = new CMy_Matrix(5, 5, "TriDiagonal Matrix");
	triD->randomiseTriDiagonalMatrix();
	b = new CMy_Matrix(1, triD->getColCount(), "B Vector");	
	b->randomiseMatrix();

	// vectors holding main, lower, and upper diagonal and the b vector Ax = b
	CMy_Matrix *md=NULL, *ld=NULL, *ud=NULL; 

	
	MPI_Status status;
	int matrixSize, calculationsSuccess;


	
	if( triD->getDiagonals(md, ld, ud) == 1 ) // tries to extract the three diagonals from the matrix
	{		
		//cout << "\n After Extracting the Vectors" << flush;

							// Start Sending the values	//
		// 1. send Vector Matrix size		
		matrixSize = triD->getRowCount();
		MPI_Send(&matrixSize, 1, MPI_INT, SLAVE1 , MATRIX_DATA_TAG, MPI_COMM_WORLD);


		//cout << "\n MatrixSize is Sent" << flush;
		
		// 2. send the main diagonal
		MPI_Send(md->mat[0], matrixSize, MPI_DOUBLE, SLAVE1 , MATRIX_DATA_TAG, MPI_COMM_WORLD);		
		// 3. send the lower diagonal
		MPI_Send(ld->mat[0], matrixSize-1, MPI_DOUBLE, SLAVE1 , MATRIX_DATA_TAG, MPI_COMM_WORLD);		
		// 4. send the upper diagonal
		MPI_Send(ud->mat[0], matrixSize-1, MPI_DOUBLE, SLAVE1 , MATRIX_DATA_TAG, MPI_COMM_WORLD);		
		// 5. send the b vector
		MPI_Send(b->mat[0], matrixSize, MPI_DOUBLE, SLAVE1 , MATRIX_DATA_TAG, MPI_COMM_WORLD);			


		//cout << "\n Vectros are sent" << flush;
		
							// Receiving the result //
		    // 1. receive the calculation result flag
		MPI_Recv(&calculationsSuccess, 1, MPI_INT, SLAVE1, MATRIX_DATA_TAG, MPI_COMM_WORLD, &status);		
		if(calculationsSuccess)
		{
			// 2. receive the result vector 
			MPI_Recv(b->mat[0], matrixSize, MPI_DOUBLE, SLAVE1 , MATRIX_DATA_TAG, MPI_COMM_WORLD, &status);
			b->printMatrix();			
		}
		else  // DGTSV function returned an error
		{
			cout << "\n Error In DGTSV" << flush;
		}			
		// delete the three internally created vectors (created by calling the getDiagonal function)
		delete md;
		delete ud;
		delete ld;
	}
}
/******************************************************************************/
void MasterWork()
{
	int function;
	// 0: Copy Code    1: compute dgtsv function
	
	function = 0;
	MPI_Send(&function, 1, MPI_INT, SLAVE1, GENERAL_TAG, MPI_COMM_WORLD);
	MasterSendCode();
	



	
	function = 1;
	MPI_Send(&function, 1, MPI_INT, SLAVE1, GENERAL_TAG, MPI_COMM_WORLD);
	MasterComputeDGTSV();
	
	



	// Signal End of Application
	function = -1;
	MPI_Send(&function, 1, MPI_INT, SLAVE1, GENERAL_TAG, MPI_COMM_WORLD);
}
/******************************************************************************/
void SlaveComputeDGTSV()
{
	//cout << "\n In SlaveComputeDGTSV()" << flush;

	// Load the DLL file Dynamically
	HINSTANCE LoadME = LoadLibrary("C:\\Work\\Developping\\Tests\\DLLTest\\Release\\DLLTest.dll");
	if (LoadME != 0)
	{
		//cout << "\n DLL library loaded\n" << flush;
		
		getFuncComputer_dgtsv LibMainEntryPointFunc;
		LibMainEntryPointFunc = (getFuncComputer_dgtsv)GetProcAddress(LoadME,"Computer_dgtsv");
		
					// start receiving the values //

		int matrixSize, calculationsSuccess;
		CMy_Matrix *md=NULL, *ld=NULL, *ud=NULL, *b=NULL; 
		MPI_Status status;

		// 1. receiving the Matrix size
		MPI_Recv(&matrixSize, 1, MPI_INT, MASTER, MATRIX_DATA_TAG, MPI_COMM_WORLD, &status);

		//cout << "\n (SLAVE) matrix size received = " << matrixSize << flush;
		
		
		// 2. receive the main diagonal
		md = new CMy_Matrix(1, matrixSize, "SLAVE MD");
		MPI_Recv(md->mat[0], matrixSize, MPI_DOUBLE, MASTER , MATRIX_DATA_TAG, MPI_COMM_WORLD, &status);
		// 3. receive the lower diagonal
		ld = new CMy_Matrix(1, matrixSize-1, "SLAVE LD");
		MPI_Recv(ld->mat[0], matrixSize-1, MPI_DOUBLE, MASTER , MATRIX_DATA_TAG, MPI_COMM_WORLD, &status);
		// 4. receive the upper diagonal
		ud = new CMy_Matrix(1, matrixSize-1, "SLAVE LD");
		MPI_Recv(ud->mat[0], matrixSize-1, MPI_DOUBLE, MASTER , MATRIX_DATA_TAG, MPI_COMM_WORLD, &status);
		// 5. receive the b vector
		b = new CMy_Matrix(1, matrixSize, "SLAVE B Vector");
		MPI_Recv(b->mat[0], matrixSize, MPI_DOUBLE, MASTER , MATRIX_DATA_TAG, MPI_COMM_WORLD, &status);
		
		
		long NRHS=1;
		calculationsSuccess = ! LibMainEntryPointFunc(matrixSize, NRHS, ld->mat[0], md->mat[0], ud->mat[0], b->mat[0], matrixSize);		
		
						// 5. send the result vector //
		//1. Sending the result FLAG
		MPI_Send(&calculationsSuccess, 1, MPI_INT, MASTER , MATRIX_DATA_TAG, MPI_COMM_WORLD);
		if(calculationsSuccess)	// 2. Sending the B vector
			MPI_Send(b->mat[0], matrixSize, MPI_DOUBLE, MASTER , MATRIX_DATA_TAG, MPI_COMM_WORLD);
				
		// delete the four internally created vectors
		delete md;
		delete ud;
		delete ld;
		delete b;

		FreeLibrary(LoadME);
	}
	else
	{
		cout << "\n DLL library failed to load\n" << flush;
	}
}
/******************************************************************************/
void SlaveReceiveCode()
{
	// This the slave thread which also coordinate tasks on the slave
	
	
	int tmp = BUFSIZ;
	MPI_Status status;

	
	char *src = new char[tmp];;	// file name
	char *buf = new char[tmp];	// data buffer
	

	// Wait to receive a DLL copy signal	
	MPI_Recv(src, BUFSIZ, MPI_CHAR, MASTER, Start_Code_Coping_TAG, MPI_COMM_WORLD, &status);
	

	
	// open a file to write data and start receiving code the file name signal 
	FILE            *fo;
	fo = fopen( src, "wb" );
	if  (fo == NULL) 
	{		
		if (fo != NULL) fclose(fo);
		cout << "\n Slave could not create the file " << flush;
		return;
	}

	// 1. receive the first message size
	MPI_Recv(&tmp, 1, MPI_INT, MASTER, End_Code_Coping_TAG, MPI_COMM_WORLD, &status);
	while(tmp != -1)
	{			
		MPI_Recv(buf, tmp, MPI_CHAR, MASTER, COPYING_TAG, MPI_COMM_WORLD, &status); // receive data bulk
		fwrite( buf, sizeof(char), tmp, fo ); // write data to file
		
		// 1. receive the next message size
		MPI_Recv(&tmp, 1, MPI_INT, MASTER, End_Code_Coping_TAG, MPI_COMM_WORLD, &status);		
	}
	fclose(fo);

	delete [] src;
	delete [] buf;

	// send a file copied signal to server
	tmp=1;
	MPI_Send(&tmp, 1, MPI_INT, MASTER, End_Code_Coping_TAG, MPI_COMM_WORLD);

	// add file to the firewall safe list
	//AddApplicationToFireWall(src);
	
	//cout <<"\n Slave on " << processorName << " finished" << flush;
}
/******************************************************************************/
void SlaveWork()
{
	int function, contn=1;
	// 0: Copy Code    1: compute dgtsv function
	
	MPI_Status status;
	
	
	while(contn == 1)
	{
		
		MPI_Recv(&function, 1, MPI_INT, MASTER, GENERAL_TAG, MPI_COMM_WORLD, &status);
		//cout << "\n Slave received function: " << function << flush;

		switch(function)
		{
			case 0:		SlaveReceiveCode(); break;
			case 1:		SlaveComputeDGTSV(); break;
			case -1: return;	
		}
		//MPI_Recv(&contn, 1, MPI_INT, MASTER, END_OF_APP_TAG, MPI_COMM_WORLD, &status);
	}
}
/******************************************************************************/
int main(int argc,char *argv[])
{
	CMPI_Initialise(argc, argv);

	int opCode=-1, slaveID=1;
	//double startTime_MPI=0.0, endTime_MPI;	

	if(commRank != 0)
	{
		SlaveWork();
	}

	if(commRank == 0) // I am MASTER	
	{		
		MasterWork();
	}
	CMPI_Finalise();


	//int ret = system( "mpiexec.exe -hosts 2 RPC272 MPI C:\\Work\\Developping\\Tests\\DynamicCodeCopy\\MPIGetRandomNumbers\\Release\\MPIGetRandomNumbers.exe" );

	
	cout << flush;
	return 1;
}